<script setup lang="ts">
import {
  nextTick,
  onBeforeUnmount,
  onMounted,
  ref,
  useTemplateRef,
  watch,
} from 'vue';

import { ClipboardAddon } from '@xterm/addon-clipboard';
import { FitAddon } from '@xterm/addon-fit';
import { Terminal } from '@xterm/xterm';

import '@xterm/xterm/css/xterm.css';

defineOptions({
  name: 'SystemTerminal',
});

const props = defineProps({
  isBoxResizing: {
    default: () => false,
    type: Boolean,
  },
  isFullScreen: {
    default: () => false,
    type: Boolean,
  },
  isMaxShowing: {
    default: () => false,
    type: Boolean,
  },
});
const terminalElRef = useTemplateRef<HTMLElement>('terminalElRef');
const fitAddon = new FitAddon();
const themeConfig = {
    baseTheme: {
      background: '#000',
      black: '#1E1E1D',
      blue: '#5D5DD3',
      brightBlack: '#262625',
      brightBlue: '#7279FF',
      brightCyan: '#72F0FF',
      brightGreen: '#72FF72',
      brightMagenta: '#E572FF',
      brightRed: '#FF7272',
      brightWhite: '#FFFFFF',
      brightYellow: '#FFFF72',
      cyan: '#5DA5D5',
      foreground: '#F8F8F8',
      green: '#5BCC5B',
      magenta: '#BC5ED1',
      red: '#CE5C5C',
      selection: '#5DA5D533',
      white: '#F8F8F8',
      yellow: '#CCCC5B',
    },
    otherTheme: {
      background: '#282a36',
      black: '#282a36',
      blue: '#57c7ff',
      brightBlack: '#686868',
      brightBlue: '#57c7ff',
      brightCyan: '#9aedfe',
      brightGreen: '#5af78e',
      brightMagenta: '#ff6ac1',
      brightRed: '#ff5c57',
      brightWhite: '#eff0eb',
      brightYellow: '#f3f99d',
      cyan: '#9aedfe',
      foreground: '#eff0eb',
      green: '#5af78e',
      magenta: '#ff6ac1',
      red: '#ff5c57',
      selection: '#97979b33',
      white: '#f1f1f0',
      yellow: '#f3f99d',
    },
  }
const baseUrl = '/terminal-server';
const theme = themeConfig.baseTheme;
const terminal = new Terminal({
  allowProposedApi: true,
  // 光标闪烁
  cursorBlink: true,
  fontFamily: '"Cascadia Code", Menlo, monospace', // "Monaco, Menlo, Consolas, 'Courier New', monospace",
  scrollback: 100,
  tabStopWidth: 4,
  theme,
});
let command: string = ''; // 输入内容
let pid: string;
let socketURL: string = `/ws-server/terminals/`;
let webSocket: WebSocket;
const resumeTime = ref(0); // 服务启动次数

const runFakeTerminal = () => {
  terminal?.writeln(
    [' ───────────────────────  \u001B[31m😉  Web Terminal 👋 \u001B[0m  ──────────────────────────'].join('\n\r'),
  );
};

const onMessage = (message: any) => {
  const data = message.data;
  terminal?.write(data);
  terminal?.element && terminal.focus();
};

const errorRealTerminal = (ex: any) => {
  let message = ex.message;
  if (!message) message = 'disconnected';
  terminal?.writeln(`\u001B[31m${message}\u001B[m\r\n`);
};

const closeRealTerminal = () => {
  terminal?.writeln('\r\n连接已断开');
};

const openRealTerminal = () => {
  command = '';
  terminal?.onData((e) => {
    switch (e) {
      case '\u0003': {
        // Ctrl+C
        terminal.write('^C');
        break;
      }
      case '\r': {
        const len = command.length ?? 0;
        for (let i = 0; i < len; i++) {
          terminal.write('\b \b');
        }
        // Enter
        webSocket?.send(`${command}\r`);
        command = '';
        break;
      }
      case '\u007F': {
        // Backspace (DEL)
        // Do not delete the prompt
        if (command.length > 0) {
          command = command.slice(0, Math.max(0, command.length - 1));
          terminal.write('\b \b');
        }
        break;
      }
      default: {
        // Print all other characters for demo
        // 0x7e = 126
        if (
          (e >= String.fromCodePoint(0x20) && e <= String.fromCodePoint(126)) ||
          e >= '\u00A0'
        ) {
          command += e;
          terminal.write(e);
        }
      }
    }
  });
};

const createWebSocket = () => {
  fetch(`${baseUrl}/terminals?cols=${terminal.cols}&rows=${terminal.rows}`, {
    method: 'POST',
  })
    .then((res) => {
      res.text().then((processId) => {
        pid = processId;
        socketURL += processId;
        webSocket = new WebSocket(socketURL);
        webSocket.addEventListener('open', openRealTerminal);
        webSocket.addEventListener('message', onMessage);
        webSocket.addEventListener('close', closeRealTerminal);
        webSocket.addEventListener('error', errorRealTerminal);
      });
    })
    .catch((error) => {
      resumeTime.value++;
      if (resumeTime.value > 3) {
        terminal?.writeln(
          `\r\n\u001B[1;32m连接失败超过3次，请手动开启服务\u001B[m`,
        );
      } else {
        createWebSocket();
        const msg = `服务未启动，第${resumeTime.value}次尝试启动服务...:${error.message}`;
        terminal?.writeln(`\r\n\u001B[31m${msg}\u001B[m`);
      }
    });
};

const initWebSocket = () => {
  if (!webSocket) {
    createWebSocket();
  }
  if (webSocket && webSocket.readyState > 1) {
    webSocket.close();
    createWebSocket();
  }
};

const initTerm = () => {
  terminal.open(terminalElRef.value as HTMLElement);
  const clipboardAddon = new ClipboardAddon();
  terminal.loadAddon(clipboardAddon);
  terminal.loadAddon(fitAddon);
  // 不能初始化的时候fit,需要等terminal准备就绪,可以设置延时操作
  setTimeout(() => {
    fitAddon.fit();
  }, 300);
};

terminal.onResize((size) => {
  // pid不存在或rows小于3的，不做处理
  if (!pid || size.rows < 3) {
    return;
  }
  const cols = size.cols;
  const rows = size.rows;
  const url = `${baseUrl}/terminals/${pid}/size?cols=${cols}&rows=${rows}`;
  fetch(url, { method: 'POST' });
});

onMounted(() => {
  initWebSocket();
  initTerm();
  runFakeTerminal();
  nextTick(() => {
    document.querySelector('.xterm')?.addEventListener('wheel', (e) => {
      if (terminal.buffer.active.baseY > 0) {
        e.preventDefault();
      }
    });
  });
});
// 关闭webSocket
onBeforeUnmount(() => {
  webSocket && webSocket?.close(1000, 'closing');
});

watch(
  () => [props.isBoxResizing, props.isFullScreen, props.isMaxShowing],
  () => {
    setTimeout(() => {
      fitAddon.fit();
    }, 500);
  },
);
</script>

<template>
  <div class="system-terminal"
    :style="{ '--terminal-theme-background': theme.background }">
    <div ref="terminalElRef"></div>
  </div>
</template>
<style scoped>
.system-terminal {
  position: relative;
  width: 100%;
  height: 100%;
  background-color: var(--terminal-theme-background);  
}
.system-terminal > div {
    width: 100%;
    height: 100%;
  }
</style>
